﻿using System.Linq.Expressions;
using System.Reflection;
using Postnuclear.Dependency.Tree;

namespace Postnuclear.Dependency.Visitors
  {
  /// <summary>
  /// Implements the visitor pattern to iterate through the expression tree.
  /// Takes the expression, gets the objects and properties used in it
  /// and creates the structure which allows to track any change in the expression compound elements.
  /// </summary>
  internal sealed class DependencyExpressionVisitor
    : ExpressionVisitor
    {
    #region Public

    // Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="DependencyExpressionVisitor"/> class.
    /// </summary>
    /// <param name="builder">The builder.</param>
    public DependencyExpressionVisitor(DependencyTreeBuilder builder)
      {
      Builder = builder;
      }

    #endregion

    #region Protected

    /// <summary>
    /// Visits the <see cref="T:System.Linq.Expressions.ConstantExpression"/>.
    /// </summary>
    /// <param name="node">The expression to visit.</param>
    /// <returns>
    /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
    /// </returns>
    protected override Expression VisitConstant(ConstantExpression node)
      {
      Builder.RegisterConstant(node.Value);
      return base.VisitConstant(node);
      }

    /// <summary>
    /// Visits the children of the <see cref="T:System.Linq.Expressions.MemberExpression"/>.
    /// </summary>
    /// <param name="node">The expression to visit.</param>
    /// <returns>
    /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
    /// </returns>
    protected override Expression VisitMember(MemberExpression node)
      {
      if (CanTrackMember(node))
        {
        Builder.RegisterMember(node.Member);
        }
      else
        {
        Builder.ResetBranch();
        }

      return base.VisitMember(node);
      }

    #endregion

    #region Private

    // Methods

    private static bool CanTrackMember(MemberExpression node)
      {
      // Check if current node is field or property
      if (node.Member.MemberType != MemberTypes.Property && node.Member.MemberType != MemberTypes.Field)
        {
        return false;
        }

      // Check if it's static one
      if (node.Expression == null)
        {
        return false;
        }

      Expression parentNode = node.Expression;

      // Fix evaluating of parent node for generic members
      if (parentNode.NodeType == ExpressionType.Convert)
        {
        if (node.Member.DeclaringType.IsAssignableFrom(node.Expression.Type))
          {
          parentNode = ((UnaryExpression)node.Expression).Operand;
          }
        else
          {
          return false;
          }
        }

      // Check if parent node is constant or property or field.
      if (parentNode.NodeType != ExpressionType.Constant && parentNode.NodeType != ExpressionType.MemberAccess)
        {
        return false;
        }

      // Check if parent is a value type (no sense to track copy of a value, while real instance stays non-tracked)
      if (parentNode.Type.IsValueType)
        {
        return false;
        }

      return true;
      }

    // Fields

    private readonly DependencyTreeBuilder Builder;

    #endregion
    }
  }